#if DEBUG
#define TRACE
#endif

#region Using Statements
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;
using Microsoft.Xna.Framework.Net;
using System.IO;
using Microsoft.Xna.Framework.GamerServices;
#endregion

namespace Pauliver
{
    public class NetworkManager
    {
        public static int NETCODEREVISION = 11;
        public static short NOPACKETREAD = 666;
        public static short IGNORE = 999;

        public class SocketThread
        {
            public SocketThread()
            {

            }

            public void Run()
            {

            }
        }

        protected IAsyncResult currentresult = null;

        protected bool imSending = false;

        public bool LastPacketWasFromMe
        {
            get
            {
                return imSending;
            }
        }

        public delegate bool NetworkCallback(PacketReader pr);
        public delegate void PlayerCallback(NetworkGamer player);
        public delegate void SessionEndedCallback(String reason);
        public delegate void HostChangedCallback(NetworkGamer oldHost,NetworkGamer newHost);
        public delegate bool NetworkManagerCallback(PacketReader pr, NetworkGamer player);

        public delegate void CallBack();
        public delegate void SessionJoinedCallback(bool connected);
        public delegate void AvailableSessionsCallback(AvailableNetworkSessionCollection thing);

        public delegate void ExceptionCallback(Exception ex);


        protected int maxGamers = 4;
        protected int maxLocalGamers = 4;

        public int MaxPlayers
        {
            get 
            {
                return maxGamers;
            }
            internal set
            {
                maxGamers = value;
            }
        }
        public int MaxPlayersPerConsole
        {
            get
            {
                return maxLocalGamers;
            }
            internal set
            {
                maxLocalGamers = value;
            }
        }

        protected AvailableNetworkSessionCollection ansc = null;

        public NetworkSession NetworkSession
        {
            get
            {
                return networkSession;
            }
        }

        NetworkSession networkSession;

        public NetworkGamer MyNetworkGamer
        {
            get
            {
                return networkSession.LocalGamers[0];
            }
        }

        PacketWriter packetWriter = new PacketWriter();
        PacketReader packetReader = new PacketReader();
        
        public enum NetworkState
        {
            Client, Host, Disconnected, NotConnected, Host_InProgress, Client_InProgress, Searching_InProgress, Searching_Found
        };

        NetworkState _CurrentState;

        protected static NetworkManager instance;        

        public static NetworkManager Instance
        {
            get
            {
                if(instance == null)
                {
                    instance = new NetworkManager();
                }
                return instance;
            }
        }

        public static bool WasUsed
        {
            get
            {
                if (instance == null)
                {
                    return false;
                }
                return !(instance._CurrentState == NetworkState.NotConnected);
            }
        }

        public static bool IsUsed
        {
            get
            {
                if(instance == null)
                {
                    return false;
                }
                else if (instance._CurrentState == NetworkState.Host || instance._CurrentState == NetworkState.Client)
                {
                    return true;
                }else{
                    return false;
                }
            }
        }

        protected NetworkManager()
        {
            _CurrentState = NetworkState.NotConnected;
            InTurn = false;
        }

        public int PlayerCount
        {
            get
            {
                if (networkSession != null)
                {
                    return networkSession.AllGamers.Count;
                }
                else
                {
                    return 0;
                }
            }
        }

        public NetworkState CurrentState
        {
            get
            {
                return _CurrentState;
            }
            internal set
            {
                _CurrentState = value;
            }
        }

        System.Object GetNextToken()
        {
            return tokens.Dequeue();
        }
        

        public T GetNextToken<T>()
        {
            if (tokens.Count < 1)
            {
                Update();
            }
            T retval = (T)tokens.Dequeue();
            return retval;
        }
        

        System.Collections.Queue tokens = new System.Collections.Queue();
        
        System.Collections.Hashtable Callbacks = new System.Collections.Hashtable();

        // If we start a turn don't stop till we have read everything
        public const short STARTTURN = 0x00;
        // This is for the end of all the packets from 1 turn;
        public const short ENDTURN = 0x01;
        public const short STRING = 0x02;
        public const short INT = 0x03;
        public const short BOOL = 0x04;
        public const short DOUBLE = 0x05;
        public const short CHAR = 0x06;
        public const short VECTOR2 = 0x07;
        public const short VECTOR3 = 0x08;



        public void RemoveCalback(short callback)
        {
            if(Callbacks.Contains(callback))
                Callbacks.Remove(callback);
        }

        public void RemoveAllCallbacks()
        {
            Callbacks.Clear();
        }

        // These callbacks are part of the NetworkSession
        public const short PLAYERJOINED = 0x10;
        public const short PLAYERLEFT = 0x11;
        public const short GAMEENDED = 0x12;
        public const short GAMESTARTED = 0x13;
        public const short NETWORKSESSIONENDED = 0x14;
        public const short HOSTCHANGED = 0x15;

        //These are async callbacks
        public const short SESSIONCREATED = 0x16;
        public const short SESSIONJOINED = 0x17;
        public const short SESSIONSFOUND = 0x18;

        bool HasData = false;

        public bool HasDataThisUpdate()
        {
            return HasData;
        }

        public void AddSessionsFoundCallback(AvailableSessionsCallback callback)
        {
            Callbacks[SESSIONCREATED] = callback;
        }

        public void AddSessionCreatedCallback(Callback callback)
        {
            Callbacks[SESSIONCREATED] = callback;
        }

        public void AddSessionJoinedCallback(SessionJoinedCallback callback)
        {
            Callbacks[SESSIONJOINED] = callback;
        }

        public void AddCallback(short callback, NetworkManagerCallback nmcallback)
        {
            Callbacks[nmcallback] = callback;
        }

        public void AddPCallback(short callback, PlayerCallback PlayerCallback)
        {
            Callbacks[callback] = PlayerCallback;
        }

        public void AddNCallBack(short callback, NetworkCallback NetworkCallback)
        {
            Callbacks[callback] = NetworkCallback;
        }

        public void AddPlayerJoinedCallback(PlayerCallback NetworkCallback)
        {
            Callbacks[PLAYERJOINED] = NetworkCallback;
        }
        public void AddPlayerLeftCallback(PlayerCallback NetworkCallback)
        {
            Callbacks[PLAYERLEFT] = NetworkCallback;
        }
        public void AddGameEndCallback(Callback NetworkCallback)
        {
            Callbacks[GAMEENDED] = NetworkCallback;
        }
        public void AddGameStartCallback(Callback NetworkCallback)
        {
            Callbacks[GAMESTARTED] = NetworkCallback;
        }
        public void AddNetworkSessionEndedCallback(SessionEndedCallback NetworkCallback)
        {
            Callbacks[NETWORKSESSIONENDED] = NetworkCallback;
        }
        public void AddHostChangedCallback(HostChangedCallback HostChanged)
        {
            Callbacks[HOSTCHANGED] = HostChanged;
        }
        

        public void SendToAll(short data)
        {
            PacketWriter pw = new PacketWriter();
            pw.Write(data);

            LocalNetworkGamer Me = (LocalNetworkGamer)networkSession.LocalGamers[0];
            Me.SendData(pw, SendDataOptions.ReliableInOrder);
        }
        
        public void SendToAll(PacketWriter pw)
        {
            LocalNetworkGamer Me = (LocalNetworkGamer)networkSession.LocalGamers[0];
            Me.SendData(pw, SendDataOptions.ReliableInOrder);
        }

        public void SendToHost(string data)
        {
            PacketWriter pw = new PacketWriter();
            pw.Write(data);
            LocalNetworkGamer Me = (LocalNetworkGamer)networkSession.LocalGamers[0];
            NetworkGamer Host = networkSession.Host;
            Me.SendData(pw, SendDataOptions.ReliableInOrder, Host);
        }
        
        public void SendToHost(PacketWriter pw)
        {
            LocalNetworkGamer Me = (LocalNetworkGamer)networkSession.LocalGamers[0];
            NetworkGamer Host = networkSession.Host;
            Me.SendData(pw, SendDataOptions.ReliableInOrder, Host);
        }
        
        /*
        public void SendToAllExcept(NetworkGamer gamer, PacketWriter pw)
        {
            //Need to subclass a PacketWriter so we can Clone it 
            LocalNetworkGamer Me = (LocalNetworkGamer)networkSession.LocalGamers[0];
            foreach (NetworkGamer g in networkSession.AllGamers)
            {
                if (g != gamer && g != Me)
                {
                    Me.SendData(pw, SendDataOptions.ReliableInOrder, g);
                }
            }
        }
        */

        public void SendToTarget(NetworkGamer gamer, PacketWriter pw)
        {
            LocalNetworkGamer Me = (LocalNetworkGamer)networkSession.LocalGamers[0];
            if(Me != gamer)
                Me.SendData(pw, SendDataOptions.ReliableInOrder, gamer);
        }

        public void StartGame()
        {
            networkSession.StartGame();
        }

        public void EndGame()
        {
            networkSession.EndGame();
        }

        public void Disconnect()
        {
            if (networkSession != null)
            {
                if (!networkSession.IsDisposed)
                {
                    networkSession.Dispose();
                }
                _CurrentState = NetworkState.Disconnected;
                networkSession = null;
            }
            instance = new NetworkManager();
        }

        protected bool ReadNonCallback(short type, PacketReader PR)
        {
            do{
                switch (type)
                {
                    case STRING:
                        tokens.Enqueue(PR.ReadString());
                        break;
                    case INT:
                        tokens.Enqueue(PR.ReadInt32());
                        break;
                    case BOOL:
                        tokens.Enqueue(PR.ReadBoolean());
                        break;
                    case DOUBLE:
                        tokens.Enqueue(PR.ReadDouble());
                        break;
                    case CHAR:
                        tokens.Enqueue(PR.ReadChar());
                        break;
                    case VECTOR2:
                        tokens.Enqueue(PR.ReadVector2());
                        break;
                    case VECTOR3:
                        tokens.Enqueue(PR.ReadVector3());
                        break;
                    default:
                        break;
                }
                if (PR.Position < PR.Length)
                {
                    try
                    {
                        type = PR.ReadInt16();
                    }
                    catch (Exception ex)
                    {
                        string exception = ex.ToString();
                        PR.Position = PR.Length;
                        type = NOPACKETREAD;
                    }
                }
                else
                {
                    type = NOPACKETREAD;
                }
            } while (type != NOPACKETREAD);
            return true;
        }

        protected bool ReadNextToken(PacketReader PR,NetworkGamer sender)
        {
            bool KeepReading = false;
            if (PR.Position < PR.Length)
            {
                short data = PR.ReadInt16();
                if (Callbacks.ContainsKey(data))
                {
                    Object callback = Callbacks[data];
                    if (callback is NetworkCallback)
                    {
                        KeepReading = (callback as NetworkCallback).Invoke(PR);
                    }
                    else if (callback is PlayerCallback)
                    {
                        (callback as PlayerCallback).Invoke(sender);
                        KeepReading = true;
                    }
                    else if (callback is NetworkManagerCallback)
                    {
                        KeepReading = (callback as NetworkManagerCallback).Invoke(PR, sender);
                    }else{
                        throw new Exception("WTF");
                    }
                }
                else if (data == ENDTURN)
                {
                    InTurn = false;
                    KeepReading = false;
                }
                else if (data == STARTTURN)
                {
                    InTurn = true;
                    KeepReading = true;
                }
                else if (data == IGNORE)
                {
                    KeepReading = true;
                }
                else
                {
                    KeepReading = ReadNonCallback(data, PR);
                }
            }
            return KeepReading;
        }

        bool InTurn = false;

        public void Update()
        {
            imSending = false;
            HasData = false;
            if (networkSession != null)
            {
                if (!networkSession.IsDisposed)
                {
                    networkSession.Update();
                    if(networkSession == null)
                    {
                        return;
                    }
                    bool KeepReading = true;
                    LocalNetworkGamer lng = networkSession.LocalGamers[0];

                    while (KeepReading)
                    {
                        NetworkGamer sender = null;
                        int bytesread = 0;
                        if (lng.IsDataAvailable)
                        {
                            bytesread = lng.ReceiveData(packetReader, out sender);
                        }

                        if (sender != null && sender == networkSession.LocalGamers[0])
                        {
                            imSending = true;
                            packetReader = null;
                            packetReader = new PacketReader();
                            KeepReading = true;
                        }
                        else if (bytesread > 0)
                        {
                            imSending = false;
                            HasData = true;
                            bool retval = ReadNextToken(packetReader, sender);
                            KeepReading = retval;
                            if (!(retval == InTurn))
                            {
                                // edge case, in case some read function returns false..
                                // when it happens i'll start plugging in code
                            }
                        }
                        else if (InTurn)
                        {
                            System.Threading.Thread.Sleep(15);
                            if (NetworkSession != null)
                            {
                                networkSession.Update();
                                KeepReading = true;
                            }
                            else
                            {
                                KeepReading = false;
                            }
                        }
                        else
                        {
                            KeepReading = false;
                        }
                    }
                }
            }
        }

        protected void ClearANSC()
        {
            if (currentresult != null && !currentresult.IsCompleted)
            {
                try
                {
                    currentresult.AsyncWaitHandle.WaitOne(1, false); //an article online says if you pass true this always throws an exception, so always pass false
                }catch(Exception ex)
                {
                    string exception = ex.ToString();
                }
                currentresult = null;
            }

            if(ansc != null)
            {
                if(!ansc.IsDisposed)
                {
                    ansc.Dispose();
                }
                ansc = null;
            }
        }
        protected void ClearNS()
        {
            if (currentresult != null && !currentresult.IsCompleted)
            {
                try
                {
                    currentresult.AsyncWaitHandle.WaitOne(1, false); //an article online says if you pass true this always throws an exception, so always pass false
                }
                catch (Exception ex)
                {
                    string exception = ex.ToString();
                }
                currentresult = null;
            }

            if(networkSession != null)
            {
                if(!networkSession.IsDisposed)
                {
                    networkSession.Dispose();
                    _CurrentState = NetworkState.NotConnected;
                }
                networkSession = null;
            }
        }

        protected void ClearAll()
        {
            ClearANSC();
            ClearNS();
        }

        public void StartServer()
        {
            ClearAll();
            _CurrentState = NetworkState.Host_InProgress;
            currentresult = NetworkSession.BeginCreate(NetworkSessionType.SystemLink, maxLocalGamers, maxGamers, SessionCreatedCallback, null);
        }
        
        public void StartServer(Callback callback)
        {
            ClearAll();
            _CurrentState = NetworkState.Host_InProgress;
            currentresult = NetworkSession.BeginCreate(NetworkSessionType.SystemLink, maxLocalGamers, maxGamers, SessionCreatedCallback, callback);
        }

        public void StartServer(int[] args)
        {
            StartServer(args, null);
        }

        public void StartServer(int[] args, Callback callback)
        {
            ClearAll();
            _CurrentState = NetworkState.Host_InProgress;

            NetworkSessionProperties property = new NetworkSessionProperties();
            int length = 8;
            if (args.Length <= 8)
            {
                length = args.Length;
            }
            for (int i = 0; i < 8; ++i)
            {
                property[i] = args[i];
            }
            currentresult = NetworkSession.BeginCreate(NetworkSessionType.SystemLink, maxLocalGamers, maxGamers, 0, property, SessionCreatedCallback, callback);
        }

        public void SessionCreatedCallback(IAsyncResult result)
        {
            try
            {
                networkSession = NetworkSession.EndCreate(result);
                _CurrentState = NetworkState.Host;
                HookSessionEvents();
            }
            catch(Exception ex)
            {
                _CurrentState = NetworkState.Disconnected;
                String exception = ex.ToString();
                networkSession = null;
            }
            currentresult = null;
            if (Callbacks.Contains(SESSIONCREATED))
            {
                (Callbacks[SESSIONCREATED] as Callback).Invoke();
            }
            if (result.AsyncState != null)
            {
                (result.AsyncState as Callback).Invoke();
            }
        }

        public void GetAvailableSessions()
        {
            ClearAll();
            _CurrentState = NetworkState.Searching_InProgress;
            currentresult = NetworkSession.BeginFind(NetworkSessionType.SystemLink, maxLocalGamers, null, SessionsFoundCallback, null);
        }

        public void GetAvailableSessions(AvailableSessionsCallback callback)
        {
            ClearAll();
            _CurrentState = NetworkState.Searching_InProgress;
            currentresult = NetworkSession.BeginFind(NetworkSessionType.SystemLink, maxLocalGamers, null, SessionsFoundCallback, callback);
        }

        public void GetAvailableSessions(AvailableSessionsCallback callback, AvailableNetworkSessionCollection current)
        {
            current = null;
            GetAvailableSessions(callback);
        }

        public void SessionsFoundCallback(IAsyncResult result)
        {
            try
            {
                ansc = NetworkSession.EndFind(result); 
                currentresult = null;
            }catch(Exception ex)
            {
                string exception = ex.ToString();
                ansc = null;
            }
            _CurrentState = NetworkState.Searching_Found;
            if (Callbacks.Contains(SESSIONSFOUND))
            {
                (Callbacks[SESSIONSFOUND] as AvailableSessionsCallback).Invoke(ansc);
            }
            if (result.AsyncState != null)
            {
                if (result.AsyncState is Pair<AvailableSessionsCallback, AvailableNetworkSessionCollection>)
                {
                    (result.AsyncState as Pair<AvailableSessionsCallback, AvailableNetworkSessionCollection>).First.Invoke(ansc);
                }
                else
                {
                    (result.AsyncState as AvailableSessionsCallback).Invoke(ansc);
                }
            }
        }

        public void Connect(int i)
        {
            ClearAll();
            _CurrentState = NetworkState.Client_InProgress;
            ansc = NetworkSession.Find(NetworkSessionType.SystemLink, maxLocalGamers, null);
            currentresult = NetworkSession.BeginJoin(ansc[i], ConnectCallback, null);
        }

        public void Connect(int i, AvailableNetworkSessionCollection availableSessions)
        {
            _CurrentState = NetworkState.Client_InProgress;
            currentresult = NetworkSession.BeginJoin(availableSessions[i], ConnectCallback, null);
        }

        public void Connect(int i, SessionJoinedCallback callback)
        {
            ClearAll();
            _CurrentState = NetworkState.Client_InProgress;
            AvailableNetworkSessionCollection availableSessions = NetworkSession.Find(NetworkSessionType.SystemLink, maxLocalGamers, null);
            if (i > availableSessions.Count || availableSessions.IsDisposed || availableSessions.Count == 0)
            {
                callback.Invoke(false);
                return;
            }
            currentresult = NetworkSession.BeginJoin(availableSessions[i], ConnectCallback, callback);
        }

        public void Connect(int i, SessionJoinedCallback callback, AvailableNetworkSessionCollection availableSessions)
        {

            if (currentresult != null && !currentresult.IsCompleted)
            {
                try
                {
                    currentresult.AsyncWaitHandle.WaitOne(1, false); //an article online says if you pass true this always throws an exception, so always pass false
                }
                catch (Exception ex)
                {
                    string exception = ex.ToString();
                }
                currentresult = null;
            }

            _CurrentState = NetworkState.Client_InProgress;
            if (availableSessions == null || availableSessions.IsDisposed || i > availableSessions.Count || availableSessions.Count == 0)
            {
                callback.Invoke(false);
                _CurrentState = NetworkState.Disconnected;
                return;
            }
            try
            {
                currentresult = NetworkSession.BeginJoin(availableSessions[i], ConnectCallback, callback);
                _CurrentState = NetworkState.Client_InProgress;
            }
            catch (Exception ex)
            {
                string exception = ex.ToString();
                _CurrentState = NetworkState.Disconnected;
                if (Callbacks[SESSIONJOINED] != null)
                {
                    (Callbacks[SESSIONJOINED] as SessionJoinedCallback).Invoke(false);
                }
                if (callback != null)
                {
                    (callback as SessionJoinedCallback).Invoke(false);
                }
            }
        }

        public void ConnectCallback(IAsyncResult result)
        {
            try
            {
                networkSession = NetworkSession.EndJoin(result);
                _CurrentState = NetworkState.Client;
                currentresult = null;
            }catch(Exception ex)
            {
                _CurrentState = NetworkState.Disconnected;
                string temp = ex.ToString();
                if (Callbacks[SESSIONJOINED] != null)
                {
                    (Callbacks[SESSIONJOINED] as SessionJoinedCallback).Invoke(false);
                }
                if (result.AsyncState != null)
                {
                    (result.AsyncState as SessionJoinedCallback).Invoke(false);
                }
                return;
            }
            if(Callbacks[SESSIONJOINED] != null)
            {
                (Callbacks[SESSIONJOINED] as SessionJoinedCallback).Invoke(true);
            }
            if(result.AsyncState != null)
            {
                (result.AsyncState as SessionJoinedCallback).Invoke(true);
            }
            HookSessionEvents();
        }

        
        public bool IsHost()
        {
            if (networkSession == null)
                return false;
            if (networkSession.IsHost)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        
        void HookSessionEvents()
        {
            networkSession.GamerJoined += GamerJoinedEventHandler;
            networkSession.GamerLeft += GamerLeftEventHandler;
            networkSession.GameStarted += GameStartedEventHandler;
            networkSession.GameEnded += GameEndedEventHandler;
            networkSession.SessionEnded += SessionEndedEventHandler;
            networkSession.HostChanged += HostChangedEventHandler;
        }

        void HostChangedEventHandler(object sender, HostChangedEventArgs e)
        {
            if (Callbacks.Contains(HOSTCHANGED))
                (Callbacks[HOSTCHANGED] as HostChangedCallback).Invoke(e.NewHost,e.OldHost);
        }

        void GamerLeftEventHandler(object sender, GamerLeftEventArgs e)
        {
            if (Callbacks.Contains(PLAYERLEFT))
                (Callbacks[PLAYERLEFT] as PlayerCallback).Invoke(e.Gamer);
        }

        void GameStartedEventHandler(object sender, GameStartedEventArgs e)
        {
            if (Callbacks.Contains(GAMESTARTED))
                (Callbacks[GAMESTARTED] as Callback).Invoke();
        }

        void GameEndedEventHandler(object sender, GameEndedEventArgs e)
        {
            if (Callbacks.Contains(GAMEENDED))
                (Callbacks[GAMEENDED] as Callback).Invoke();
        }

        void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e)
        {
            string errorMessage = e.EndReason.ToString();
            _CurrentState = NetworkState.Disconnected;
            if (Callbacks.Contains(NETWORKSESSIONENDED))
            {
                if (Callbacks[NETWORKSESSIONENDED] is SessionEndedCallback)
                {
                    (Callbacks[NETWORKSESSIONENDED] as SessionEndedCallback).Invoke(errorMessage);
                }else if(Callbacks[NETWORKSESSIONENDED] is CallBack)
                {
                    (Callbacks[NETWORKSESSIONENDED] as CallBack).Invoke();
                }
            }
            if (networkSession != null)
            {
                if (!networkSession.IsDisposed)
                {
                    networkSession.Dispose();
                }
                if (networkSession != null)
                {
                    networkSession = null;
                }
            }
        }

        void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e)
        {
            System.Threading.Thread.Sleep(10);
            if (Callbacks.Contains(PLAYERJOINED))
                (Callbacks[PLAYERJOINED] as PlayerCallback).Invoke(e.Gamer);
        }
    }
}
